﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using ReporterEngine.Model;

namespace ReporterEngine
{
    public class CIFModel : Model<XElement>
    {
        public XElement Notebook;
        
        protected override string GetAttributeValue(string name, XElement node)
        {
            throw new NotImplementedException();
        }

        public override void Parse(XDocument source)
        {
            //load by ids
            var idels = source.Descendants().Where(x => x.Attribute("id") != null);
            foreach (var idel in idels)
            {
                if (idel.Attribute("id").Value != "")
                    IDs.Add(idel.Attribute("id").Value,idel);
            }

            //load by refids
            var refels = source.Descendants().Where(x => x.Attribute("refid") != null);
            foreach (var idel in refels)
            {
                var refid = idel.Attribute("refid").Value;
                List<XElement> lis = null;

                if (!REFs.ContainsKey(refid))
                {
                    lis = new List<XElement>();
                    REFs.Add(refid, lis);
                }
                else
                    lis = REFs[refid];


                lis.Add(idel);
            }

            //prepare element types
            var elements = source.Element("cif").Element("notebook").Element("instances")
                .Elements().Where(x => x.Attribute("setLabel") != null);

           
           ModelTypes.AddRange(elements.Select(item => CreateNodeType(item)));

            Notebook = source.Element("cif").Element("notebook");

        }

        private MetaModelType CreateNodeType(XElement item)
        {
            var modeltype = CreateNodeType();
            modeltype.PluralLabel = item.Attribute("setLabel").Value;
            modeltype.Label = item.Attribute("label").Value;
            modeltype.Data = item;

            modeltype.Nodes.AddRange(item.Elements());


            return modeltype;
        }

        //public override sQuery Eval(string path)
        //{
        //    sQuery q = sQuery.New(this);

        //    return Eval(path, q);
        //}

        public override string GetPropertyName(XElement node)
        {
            return node.Name.LocalName;
        }

        public string GetFullName(XElement node)
        {
            var result = GetName(node);
            return result;
        }

        public string GetName(XElement node)
        {
            var parent = node;
            while (parent != null)
            {
                if (parent.Attribute("name") != null)
                    return parent.Attribute("name").Value;

                parent = parent.Parent;
            }

            return "undefined";
        }

        public string GetElemenStringtID(XElement element)
        {
            return GetElemenStringtID(element, null);
        }



        //public string GetElemenStringtID(XElement element, sQuery query)
        //{
        //    var numbering = "";

          

        //    if (element != null && element.Element("numberPrefix") != null)
        //    {
        //        var parent = element;

        //        while (parent != null && parent.Element("numberPrefix") != null)
        //        {
        //            if (string.IsNullOrEmpty(numbering))
        //                numbering = numberPrefix(parent);
        //            else
        //                numbering = numberPrefix(parent) + "." + numbering;

        //            if (numbering == ".3.2")
        //            {
        //                int ee = 12;
        //                //vyhladavanie organization parenta
        //            }


        //            if (parent.Element("parent") != null)
        //            {
        //                var refid = parent.Element("parent").Attribute("refid").Value;
        //                parent = this.IDs[refid];

        //                if(parent.Element("numberPrefix") == null && query != null)
        //                {
        //                    parent = null;

        //                    var contextPath = LexPath.Scan(">>parent<<core");//>>parent<<core
        //                    var interpreter = Interpreter.Create(contextPath);

        //                    var contextQuery = query.NewFromElement(element);

        //                    var contextResult = interpreter.Execute(contextPath, contextQuery);

        //                    var parentElements = new List<XElement>();

        //                    contextResult.Elements.ForEach(x => parentElements.Add(x));

        //                    //find nearest id in query history
        //                    var parentQuery = contextQuery;

        //                    var allParents = new List<XElement>();

        //                    if (GetID(element) == 1294811)
        //                    {
        //                        int ee = 12;
        //                    }
                            
        //                    while(parentQuery != null && parent == null)
        //                    {
        //                        foreach(var innerEl in parentQuery.Elements)
        //                        {
        //                            if (parent == null)
        //                            {
        //                                //verify parent id not direct object, because only parent is associated to context like hierarchy
                                        
        //                                var innerElID = GetID(innerEl);//potencial parent id

        //                                foreach(var potentialNumberingEl in parentElements)
        //                                {
        //                                    //if (GetXParent(innerElID) != null parentIDs.Contains(innerElID))
        //                                    if (GetID(GetXParent(potentialNumberingEl)) == innerElID)
        //                                    {
        //                                        parent = potentialNumberingEl;
        //                                    }
        //                                }
        //                            }

        //                            allParents.Add(innerEl);
        //                        }

        //                        parentQuery = parentQuery.Parent;
        //                    }

        //                    if (GetID(element) == 1294811)
        //                    {
        //                        int ee = 12;
        //                    }
                            
                            
        //                }
        //            }
        //            else
        //                parent = null;
        //        }
        //    }

        //    if (GetID(element) == 1294811)
        //    {
        //        int ee = 12;
        //    }

        //    return numbering;
        //}

         public string GetElemenStringtID(XElement element, sQuery query)
         {
             return GetElemenID(element,query).IdToString();
         }

         public cifIDwithName GetElemenID(XElement element, sQuery query)
        {
            var idName = new cifIDwithName();
             idName.Name = GetName(element);

            if (element != null && element.Element("numberPrefix") != null)
            {
                var parent = element;

                while (parent != null && parent.Element("numberPrefix") != null)
                {
                    idName.IDs.Add(numberPrefix(parent));
                    
                    //if (string.IsNullOrEmpty(numbering))
                    //{
                    //    //numbering = numberPrefix(parent);
                    //    idName.IDs.Add(numberPrefix(parent));
                    //}
                    //else
                    //    numbering = numberPrefix(parent) + "." + numbering;

                 

                    if (parent.Element("parent") != null)
                    {
                        var refid = parent.Element("parent").Attribute("refid").Value;
                        parent = this.IDs[refid];

                        if (parent.Element("numberPrefix") == null && query != null)
                        {
                            parent = null;

                            var tempParent = this.GetXParent(element);
                            var parentElements = this.GetXUsedBy(tempParent, "core");


 //                           var contextPath = LexPath.Scan(">>parent<<core");//>>parent<<core
                            //do it manually to prevent sort and cycle


                            //var interpreter = Interpreter.Create(contextPath);

                            var contextQuery = query.NewFromElement(element);

//                            var contextResult = interpreter.Execute(contextPath, contextQuery);

//                            var parentElements = new List<XElement>();

//                            contextResult.Elements.ForEach(x => parentElements.Add(x));

                            //find nearest id in query history
                            var parentQuery = contextQuery;

                            var allParents = new List<XElement>();

                            //if (GetID(element) == 1294811)
                            //{
                            //    int ee = 12;
                            //}

                            while (parentQuery != null && parentElements.Count > 0 && parent == null)
                            {
                                foreach (var innerEl in parentElements)
                                {
                                    if (parent == null)
                                    {
                                        //verify parent id not direct object, because only parent is associated to context like hierarchy

                                        var innerElID = GetID(innerEl);//potencial parent id

                                        foreach (var potentialNumberingEl in parentElements)
                                        {
                                            //if (GetXParent(innerElID) != null parentIDs.Contains(innerElID))
                                            var xParent = GetXParent(potentialNumberingEl);
                                            if (xParent != null && GetID(xParent) == innerElID)//activity sould not countains parent reference, or invalid parent ID should be in cif model
                                            {
                                                parent = potentialNumberingEl;
                                            }
                                        }
                                    }

                                    allParents.Add(innerEl);
                                }

                                parentQuery = parentQuery.Parent;
                            }

                            if (GetID(element) == 1294811)
                            {
                                int ee = 12;
                            }


                        }
                    }
                    else
                        parent = null;
                }
            }

            if (GetID(element) == 1294811)
            {
                int ee = 12;
            }

            return idName;
        }

        public static cifIDNode numberPrefix(XElement element)
        {
            var res = new cifIDNode();

            if (element.Element("numberPrefix") != null)
            {
                string numValue = element.Element("numberPrefix").Value;

                string prefixStyle = element.Element("prefixStyle").Value;

                res.IntValue = int.Parse(numValue);
                res.IsEmpty = false;

                if (prefixStyle == "numeric")
                {
                    return res;
                }

                if (prefixStyle == "alphaExtended")
                {
                    res.IsAlpha = true;

                    if (Int64.Parse(numValue) >= 0)
                    {
                        var totalVal = Int64.Parse(numValue);

                        var currentVal = totalVal;

                        var resText = "";

                        if (totalVal == 70)
                        {
                            int ee = 12;
                        }

                        while (currentVal > 24)
                        {
                            var zvysok = currentVal % 26;
                            var podiel = (currentVal - zvysok) / 26;

                            byte curByte = (byte)zvysok;
                            curByte += (byte)'A' - 1;
                            char curChar = (char)curByte;

                            resText = curChar.ToString() + resText;

                            currentVal = podiel;
                        }


                        byte lastByte = (byte)currentVal;
                        lastByte += (byte)'A' - 1;
                        char lastChar = (char)lastByte;

                        resText = lastChar.ToString() + resText;

                        res.TextValue = resText;

                        return res;

                    }
                }
            }

            return res;
        }

        //public static string numberPrefix(XElement element)
        //{
        //    if (element.Element("numberPrefix") != null)
        //    {
        //        string numValue = element.Element("numberPrefix").Value;

        //        string prefixStyle = element.Element("prefixStyle").Value;


        //        if (prefixStyle == "numeric")
        //            return numValue;

        //        if (prefixStyle == "alphaExtended")
        //        {
        //            if (Int64.Parse(numValue) >= 0)
        //            {
        //                var totalVal = Int64.Parse(numValue);

        //                var currentVal = totalVal;

        //                var resText = "";

        //                if (totalVal == 70)
        //                {
        //                    int ee = 12;
        //                }

        //                while (currentVal > 24)
        //                {
        //                    var zvysok = currentVal % 26;
        //                    var podiel = (currentVal - zvysok) / 26;

        //                    byte curByte = (byte)zvysok;
        //                    curByte += (byte)'A' - 1;
        //                    char curChar = (char)curByte;

        //                    resText = curChar.ToString() + resText;

        //                    currentVal = podiel;
        //                }


        //                byte lastByte = (byte)currentVal;
        //                lastByte += (byte)'A' - 1;
        //                char lastChar = (char)lastByte;

        //                resText = lastChar.ToString() + resText;


        //                return resText;

        //            }
        //        }
        //    }

        //    return "";
        //}

        public override string GetTypeName(XElement node)
        {
            return node.Ancestors().Where(x=>x.Attribute("label")!= null).Select(x=>x.Attribute("label").Value).FirstOrDefault();
        }

        public string GetPlularTypeName(XElement node)
        {
            return node.Ancestors().Where(x => x.Attribute("setLabel") != null).Select(x => x.Attribute("setLabel").Value).FirstOrDefault();
        }

        public XElement GetXParent(XElement element)
        {
            if (element.Element("parent") != null)
            {
                var refid = element.Element("parent").Attribute("refid").Value;
                var parent = this.IDs[refid];

                return parent;
            }
            return null;
        }

        public XElement GetXReference(XElement element, string referenceName)
        {
            if (element.Element("referenceName") != null)
            {
                var refid = element.Element("referenceName").Attribute("refid").Value;
                var reference = this.IDs[refid];

                return reference;
            }
            return null;
        }

        public List<XElement> GetXUsedBy(XElement element, string usedByName)
        {
            List<XElement> result = new List<XElement>();
            var id = GetID(element).ToString();

            if(REFs.ContainsKey(id))
            {
                var currefElements = REFs[id];
                foreach(var currefEl in currefElements)
                {
                    if (currefEl.Name.LocalName == usedByName && currefEl.Parent.Name.LocalName != "members")
                        result.Add(currefEl.Parent);
                }
            }

            return result;
        }

        public int GetID(XElement node)
        {
            return node.Attribute("id") != null ? int.Parse(node.Attribute("id").Value) : 0;
        }

        public List<InstanceCategory> GetInstanceCategories()
        {
            var result = new List<InstanceCategory>();

            foreach (var modelType in this.ModelTypes)
            {
                var icat = new InstanceCategory { Model = this,CategoryName = modelType.PluralLabel,CategoryType = CategoryTypes.InstanceGroupType,
                InstanceGroupTypeName = modelType.Label};

                result.Add(icat);
            }

            result = result.OrderBy(x => x.CategoryName).ToList();
            
            return result;
        }

        public List<InstanceCategory> GetInstanceCategories(sQuery query)
        {
            var result = new List<InstanceCategory>();

            List<string> catNames = new List<string>();

            foreach(var node in query.Elements)
            {
                if(!catNames.Contains(GetTypeName(node)))
                {
                    catNames.Add(GetTypeName(node));
                }
            }

            foreach(var catname in catNames)
            {
                var icat = new InstanceCategory
                {
                    Model = this,
                    CategoryName = catname,
                    CategoryType = CategoryTypes.Custom,
                    InstanceGroupTypeName = catname
                };

                icat.Childs.AddRange(query.Elements.Where(x => GetTypeName(x) == catname).Select(x => new InstanceNode
                                                                                                    {
                                                                                                        Model = this, 
                                                                                                        Element = x,
                                                                                                        InstanceName = GetName(x),
                                                                                                        TypeName = GetTypeName(x)
                                                                                                    }).Cast<BaseInstanceNode>());
                icat.Childs = icat.Childs.OrderBy(x => x.GetCompareName()).ToList();


                result.Add(icat);
            }

            if(query.Values.Count > 0)
            {
                var valuesCat = new InstanceCategory
                {
                    Model = this,
                    CategoryName = "Values",
                    CategoryType = CategoryTypes.Values,
                    InstanceGroupTypeName = "Values"
                };

                foreach(var value in query.Values.Distinct())
                {
                    var newValue = new InstanceValue {Value = value};
                    valuesCat.Childs.Add(newValue);
                }

                result.Add(valuesCat);
            }

            result = result.OrderBy(x => x.CategoryName).ToList();

            return result;
        }

        public List<InstanceNode> GetInstanceNodesByTypeName(string typeName)
        {
            var result = new List<InstanceNode>();

            var modelType = this.ModelTypes.Where(
                            x => x.Label.ToLower() == typeName.ToLower() || x.PluralLabel.ToLower() == typeName.ToLower()).FirstOrDefault();

            if(modelType != null)
            {
                foreach (var mElement in modelType.Nodes)
                {
                    var instanceNode = new InstanceNode { Model = this, Element = mElement };
                    instanceNode.InstanceName = GetName(mElement);
                    instanceNode.TypeName = GetTypeName(mElement);


                    result.Add(instanceNode);
                }
            }

            result = result.OrderBy(x => x.InstanceName).ToList();
            
            return result;
        }

        public List<InstanceUsedBy> GetInstanceUsedBy(InstanceNode node)
        {
            var oid = GetID(node.Element);

            var result = new List<InstanceUsedBy>();

            if (this.REFs.ContainsKey(oid.ToString()))
            {
                foreach (var usedby in this.REFs[oid.ToString()])
                {
                    var propertyName = usedby.Name.LocalName;

                    var usedByNode = new InstanceUsedBy { PropertyName = propertyName };

                    var refElement = usedby.Parent;

                    if (refElement.Attribute("name") != null)
                    {
                        var usedByInstanceNode = new InstanceNode { Model = this, Element = refElement };
                        usedByNode.UsedByNode = usedByInstanceNode;
                        usedByNode.UsedByNode.InstanceName = GetName(refElement);
                        usedByNode.UsedByNode.TypeName = GetTypeName(refElement);

                        result.Add(usedByNode);
                    }
                }
            }

            result = result.OrderBy(x => x.GetCompareName()).ToList();

            return result;
        }

        public List<InstanceReference> GetInstanceReferencies(InstanceNode node)
        {
            var result = new List<InstanceReference>();

            foreach (var prop in node.Element.Elements().Where(x => x.Attribute("refid") != null))
            {
                var refid = prop.Attribute("refid").Value;

                if (this.IDs.ContainsKey(refid))
                {
                    var refElement = this.IDs[refid];

                    var refNode = new InstanceReference { Model = this, Element = prop };
                    refNode.PropertyName = prop.Name.LocalName;
                    
                    refNode.ReferenceNode = new InstanceNode { Model = this, Element = refElement };
                    refNode.ReferenceNode.TypeName = GetTypeName(refElement);
                    refNode.ReferenceNode.InstanceName = GetName(refElement);
                    result.Add(refNode);
                }
            }

            result = result.OrderBy(x => x.GetCompareName()).ToList();

            return result;
        }

        public List<InstanceNode> GetInstanceMembers(InstanceNode node)
        {
            var result = new List<InstanceNode>();

            if(node.Element.Element("members") != null)
            {
                foreach (var member in node.Element.Element("members").Elements())
                {
                    var refid = member.Attribute("refid").Value;

                    if (this.IDs.ContainsKey(refid))
                    {
                        var refElement = this.IDs[refid];

                        var iNode= new InstanceNode { Model = this, Element = refElement };
                        iNode.TypeName = GetTypeName(refElement);
                        iNode.InstanceName = GetName(refElement);
                        result.Add(iNode);
                    }
                }
            }

            result = result.OrderBy(x => x.GetCompareName()).ToList();

            return result;
        }

        public List<InstanceNode> GetInstanceDimensions(InstanceNode node)
        {
            var result = new List<InstanceNode>();

            if (node.Element.Element("dimensions") != null)
            {
                foreach (var member in node.Element.Element("dimensions").Elements())
                {
                    var refid = member.Attribute("refid").Value;

                    if (this.IDs.ContainsKey(refid))
                    {
                        var refElement = this.IDs[refid];

                        var iNode = new InstanceNode { Model = this, Element = refElement };
                        iNode.TypeName = GetTypeName(refElement);
                        iNode.InstanceName = GetName(refElement);
                        result.Add(iNode);
                    }
                }
            }

            result = result.OrderBy(x => x.GetCompareName()).ToList();

            return result;
        }

        public List<InstanceProperty> GetInstanceProperties(InstanceNode node)
        {
            var result = new List<InstanceProperty>();

            //insert oid, id, name, type
            result.Add(new InstanceProperty { Model = this, PropertyName = "oid", PropertyValue = GetID(node.Element).ToString() });
            result.Add(new InstanceProperty { Model = this, PropertyName = "id", PropertyValue = GetElemenStringtID(node.Element).ToString() });
            result.Add(new InstanceProperty { Model = this, PropertyName = "name", PropertyValue = GetFullName(node.Element).ToString() });
            result.Add(new InstanceProperty { Model = this, PropertyName = "type", PropertyValue = GetTypeName(node.Element).ToString() });

            foreach (var prop in node.Element.Elements().Where(x => x.Attribute("refid") == null))
            {
                var inProp = new InstanceProperty{Model = this,Element = prop};
                inProp.PropertyName = prop.Name.LocalName;

                if(prop.Elements().Count() == 0)
                    inProp.PropertyValue = prop.Value;

                result.Add(inProp);
            }

            result = result.OrderBy(x => x.GetCompareName()).ToList();

            return result;
        }


        public List<ModelType> GetModelTypes(sQuery query)
        {
            var result = new List<ModelType>();

            List<string> catNames = new List<string>();

            foreach (var node in query.Elements)
            {
                if (!catNames.Contains(GetTypeName(node)))
                {
                    catNames.Add(GetTypeName(node));
                }
            }


            foreach(var cat in catNames)
            {
                result.Add(new ModelType { Name = cat});

            }

            return result;
        }

        public List<ModelProperty> GetModelproperties(sQuery query)
        {
            var result = new List<ModelProperty>();

            List<string> propNames = new List<string>();

            foreach (var node in query.Elements)
            {
                foreach (var prop in node.Elements().Where(x => x.Attribute("refid") == null))
                {
                    if (!propNames.Contains(prop.Name.LocalName))
                    {
                        propNames.Add(prop.Name.LocalName);
                    }
                }
            }

            foreach (var cat in propNames)
            {
                result.Add(new ModelProperty { Name = cat });

            }

            return result;
        }

        public List<ModelReference> GetModelreferencies(sQuery query)
        {
            var result = new List<ModelReference>();

            List<string> propNames = new List<string>();

            foreach (var node in query.Elements)
            {
                foreach (var prop in node.Elements().Where(x => x.Attribute("refid") != null))
                {
                    if (!propNames.Contains(prop.Name.LocalName))
                    {
                        propNames.Add(prop.Name.LocalName);
                    }
                }
            }

            foreach (var cat in propNames)
            {
                result.Add(new ModelReference { Name = cat });

            }

            return result;
        }

        public List<ModelUsedBy> GetModelUsedBy(sQuery query)
        {
            var result = new List<ModelUsedBy>();

            List<string> propNames = new List<string>();

            foreach (var node in query.Elements)
            {
                var id = GetID(node);
                if(REFs.ContainsKey(id.ToString()))
                {
                    foreach (var refElement in REFs[id.ToString()])
                    {
                        if (!propNames.Contains(refElement.Name.LocalName))
                        {
                            propNames.Add(refElement.Name.LocalName);
                        }
                    }
                }
            }

            foreach (var cat in propNames)
            {
                result.Add(new ModelUsedBy { Name = cat });

            }

            return result;
        }


        public bool HasMembers(XElement element)
        {
            return element.Element("members") != null;
        }

        public bool HasAllowedChilds(XElement element)
        {
            return false;// element.Element("members") != null;
        }















        public sQuery GetNotebookQuery()
        {
            var result = sQuery.New(this);
            result.Elements.Add(this.Notebook);

            return result;
        }

        public class NodeComparer : IEqualityComparer<XElement>
        {
            public bool Equals(XElement x, XElement y)
            {
                return GetID(x).Equals(GetID(y));
            }

            public int GetHashCode(XElement obj)
            {
               return GetID(obj);
            }

            public int GetID(XElement node)
            {
                return node.Attribute("id") != null ? int.Parse(node.Attribute("id").Value) : 0;
            }
        }


        public class cifIDwithNameComparer : IComparer<cifIDwithName>
        {
            public int Compare(cifIDwithName x, cifIDwithName y)
            {
                var counts = x.IDs.Count > y.IDs.Count ? y.IDs.Count : x.IDs.Count;
                var xIDS = x.IDs.ToList();
                var yIDS = y.IDs.ToList();
                xIDS.Reverse();
                yIDS.Reverse();

                var xstr = x.IdToString();
                var ystr = y.IdToString();

              

                for (var i = 0; i < counts; i++)
                {
                    var xNum = xIDS[i].IntValue;
                    var yNum = yIDS[i].IntValue;

                    if (xNum.CompareTo(yNum) != 0)
                        return xNum.CompareTo(yNum);
                }
                if (x.IdToString() == "NTG.3.9.1.3")//x.IDs.Count == 5 && y.IDs.Count == 5 && x.IdToString() == "NTG.3.9.1.3")
                {
                    int ee = 12;
                }
            

                if (x.IDs.Count.CompareTo(y.IDs.Count) != 0) return x.IDs.Count.CompareTo(y.IDs.Count);
                
                return x.Name.CompareTo(y.Name);
            }
        }

        public class cifIDwithName : IComparable
        {
            public List<cifIDNode> IDs = new List<cifIDNode>();

            public string Name;

            public string IdToString()
            {
                var result = string.Empty;

                foreach (var id in IDs)
                {
                    if (result == "")
                        result = id.ToString();
                    else
                    {
                        result = id.ToString() + "." + result;
                    }
                }
                
                return result;
            }

            public int CompareTo(object obj)
            {
                var comparer = new cifIDwithNameComparer();

                var y = obj as cifIDwithName;

                //if (IDs.Count > 2 && IDs[0].IntValue != IDs[1].IntValue)
                //{
                //    int ee = 12;
                //}

                return comparer.Compare(y, this);
            }
        }

        public class cifIDNode
        {
            public int IntValue { get; set; }
            public bool IsAlpha { get; set; }
            public bool IsEmpty = true;
            public string TextValue { get; set; }

            public override string ToString()
            {
                if (IsEmpty)
                    return string.Empty;

                if (!IsAlpha)
                    return IntValue.ToString();
                else
                {
                    return TextValue;
                }

                return base.ToString();
            }
        }
    }
}
